package com.yanmaohu.nikita.parser;

import com.yanmaohu.nikita.NikitaException;
import com.yanmaohu.nikita.configuration.Configuration;
import com.yanmaohu.nikita.data.*;
import com.yanmaohu.nikita.data.expression.Expression;
import com.yanmaohu.nikita.data.expression.NameExpression;
import com.yanmaohu.nikita.data.function.*;
import com.yanmaohu.nikita.data.structure.*;
import com.yanmaohu.nikita.data.variable.Variable;
import com.yanmaohu.nikita.scanner.Scanner;
import com.yanmaohu.nikita.scanner.SourceCode;
import com.yanmaohu.nikita.scanner.Token;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Bright Lee
 */
public final class Parser {

    private Scanner scanner;
    private ExpressionParser expressionParser;
    private StatementParser statementParser;
    private ModifierParser modifierParser;
    private MemberParser memberParser;

    public Parser(Configuration configuration, File file) {
        scanner = new Scanner(configuration, file);
        expressionParser = new ExpressionParser(this);
        statementParser = new StatementParser(this);
        modifierParser = new ModifierParser(this);
        memberParser = new MemberParser(this);
        statementParser.setExpressionParser(expressionParser);
        statementParser.setModifierParser(modifierParser);
        statementParser.setStructureMemberParser(memberParser);
        expressionParser.setStatementParser(statementParser);
        memberParser.setExpressionParser(expressionParser);
        memberParser.setModifierParser(modifierParser);
        memberParser.setStatementParser(statementParser);
    }

    protected Scanner scanner() {
        return scanner;
    }

    public File getFile() {
        return scanner.getFile();
    }

    public SourceCode getCode() {
        return scanner.getCode();
    }

    public TypeList parse() {
        List<Use> useList = null;
        while (true) {
            mark();
            Token token = scan();
            if (!token.isUse()) {
                reset();
                break;
            }
            List<NameExpression> usedTypeName = null;
            while (true) {
                token = scan();
                if (!token.isId()) {
                    throw new NikitaException(getCode(), token, "This must be an package name or type name.");
                }
                NameExpression nameExpression = new NameExpression(token);
                if (usedTypeName == null) {
                    usedTypeName = new ArrayList<>();
                }
                usedTypeName.add(nameExpression);
                mark();
                token = scan();
                if (!token.isPeriod()) {
                    reset();
                    break;
                }
            }
            NameExpression alias = null;
            mark();
            token = scan();
            if (token.isId()) {
                alias = new NameExpression(token);
                token = scan();
            }
            if (!token.isSemicolon()) {
                throw new NikitaException(getCode(), token, "Missing a ;.");
            }
            if (useList == null) {
                useList = new ArrayList<>();
            }
            useList.add(new Use(usedTypeName, alias));
        }
        List<Type> typeList = null;
        while (true) {
            Type type = tryParseType();
            if (type == null) {
                break;
            }
            if (typeList == null) {
                typeList = new ArrayList<>();
            }
            typeList.add(type);
        }
        checkTail();
        return new TypeList(getFile(), new UseList(useList), typeList);
    }

    private Type tryParseType() {
        final int markedIndex = mark();
        final TypeModifier typeModifier = modifierParser.tryParseTypeModifier();
        final Token token = scan();
        if (!token.isId()) {
            if (typeModifier != null) {
                throw new NikitaException(getCode(), token, "This must be a structure or a function.");
            }
            reset(markedIndex);
            return null;
        }
        NameExpression nameExpression = new NameExpression(token);
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isLeftPar()) {
            reset(markedIndex1);
            ParameterList parameterList = parseParameterList();
            FunctionBody functionBody = statementParser.parseStatementList();
            return new Function(typeModifier, nameExpression, parameterList, functionBody);
        }
        if (token1.isLeftBrc()) {
            reset(markedIndex1);
            StructureBody structureBody = memberParser.parseStructureMemberList();
            return new Structure(typeModifier, nameExpression, structureBody);
        }
        if (token1.isAssign()) {
            Expression variableValue = expressionParser.parseExpression();
            Token token2 = scan();
            if (!token2.isSemicolon()) {
                throw new NikitaException(getCode(), token2.getBeginIndex(), token2.getEndIndex(), "Missing a ;.");
            }
            return new Variable(typeModifier, nameExpression, variableValue);
        }
        throw new NikitaException(getCode(), token1, "This must be a function body or a structure body.");
    }

    protected ParameterList tryParseParameterList() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLeftPar()) {
            reset(markedIndex);
            return null;
        }
        Parameter parameter = tryParseParameter();
        if (parameter == null) {
            Token token1 = scan();
            if (!token1.isRightPar()) {
                reset(markedIndex);
                return null;
            }
            return new ParameterList(token.getBeginIndex(), token1.getEndIndex(), null);
        }
        List<Parameter> list = new ArrayList<>();
        list.add(parameter);
        Token token1;
        while (true) {
            token1 = scan();
            if (!token1.isComma()) {
                break;
            }
            Parameter parameter1 = tryParseParameter();
            if (parameter1 == null) {
                reset(markedIndex);
                return null;
            }
            list.add(parameter1);
        }
        if (!token1.isRightPar()) {
            throw new NikitaException(getCode(), token1, "Missing a ).");
        }
        return new ParameterList(token.getBeginIndex(), token1.getEndIndex(), list);
    }

    protected ParameterList parseParameterList() {
        ParameterList parameterList = tryParseParameterList();
        if (parameterList == null) {
            throw new NikitaException(getCode(), scanner.scan(), "This must be a parameter list.");
        }
        return parameterList;
    }

    private Parameter tryParseParameter() {
        NameExpression parameterName = expressionParser.tryParseNameExpression();
        if (parameterName == null) {
            return null;
        }
        return new Parameter(parameterName);
    }

    protected StructureInstance tryParseStructureInstance() {
        List<NameExpression> structureName = null;
        int markedIndex = mark();
        Token token = scan();
        if (token.isId()) {
            structureName = new ArrayList<>();
            structureName.add(new NameExpression(token));
            while (true) {
                final int markedIndex1 = mark();
                final Token token1 = scan();
                if (!token1.isPeriod()) {
                    reset(markedIndex1);
                    break;
                }
                NameExpression nameExpression1 = expressionParser.parseNameExpression();
                structureName.add(nameExpression1);
            }
        } else {
            reset(markedIndex);
        }
        final Token token1 = scan();
        if (!token1.isLeftBrc()) {
            reset(markedIndex);
            return null;
        }
        List<MemberInstance> list = null;
        while (true) {
            MemberInstance memberInstance = tryParseMemberInstance();
            if (memberInstance == null) {
                break;
            }
            if (list == null) {
                list = new ArrayList<>();
            }
            list.add(memberInstance);
        }
        final Token token2 = scan();
        if (!token2.isRightBrc()) {
            throw new NikitaException(getCode(), token2, "This must be a }.");
        }
        return new StructureInstance(
                token.getBeginIndex(),
                token2.getEndIndex(),
                structureName,
                list);
    }

    private MemberInstance tryParseMemberInstance() {
        final int markedIndex = mark();
        Token token = scan();
        if (!token.isId()) {
            reset(markedIndex);
            return null;
        }
        NameExpression nameExpression = new NameExpression(token);
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isAssign()) {
            Expression variableValue = expressionParser.parseExpression();
            token1 = scan();
            if (!token1.isSemicolon()) {
                throw new NikitaException(getCode(), token1, "Missing a ;.");
            }
            return new VariableMemberInstance(nameExpression, variableValue);
        }
        if (token1.isLeftPar()) {
            reset(markedIndex1);
            ParameterList parameterList = parseParameterList();
            FunctionBody functionBody = statementParser.parseStatementList();
            int markedIndex2 = mark();
            Token token2 = scan();
            reset(markedIndex2);
            if (token2.isSemicolon()) {
                throw new NikitaException(getCode(), token2, "Delete the semicolon.");
            }
            return new FunctionMemberInstance(nameExpression, parameterList, functionBody);
        }
        throw new NikitaException(getCode(), token1, "Error token.");
    }

    protected ArgumentList parseArgumentList() {
        Token token = scan();
        if (!token.isLeftPar()) {
            throw new NikitaException(getCode(), token, "This must be a (.");
        }
        int beginIndex = token.getBeginIndex();
        List<Expression> argumentList = null;
        Expression argument = expressionParser.tryParseExpression();
        if (argument != null) {
            argumentList = new ArrayList<>();
            argumentList.add(argument);
            while (true) {
                int markedIndex = mark();
                token = scan();
                if (!token.isComma()) {
                    reset(markedIndex);
                    break;
                }
                argument = expressionParser.tryParseExpression();
                if (argument == null) {
                    Token token1 = scan();
                    throw new NikitaException(getCode(), token1, "This must be a expression.");
                }
                argumentList.add(argument);
            }
        }
        token = scan();
        if (!token.isRightPar()) {
            throw new NikitaException(getCode(), token, "This must be a ).");
        }
        return new ArgumentList(beginIndex, token.getEndIndex(), argumentList);
    }

    private void checkTail() {
        Token token = scan();
        if (!token.isEof()) {
            throw new NikitaException(getCode(), token.getBeginIndex(), getCode().length(), "Illegal content exists at the end of the source file.");
        }
    }

    int mark() {
        return scanner.mark();
    }

    Token scan() {
        return scanner.scan();
    }

    void reset(int markedIndex) {
        scanner.reset(markedIndex);
    }

    void reset() {
        scanner.reset();
    }

}
